package view;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.URL;
import java.util.*;
import javax.swing.*;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;

import model.CategoryDataModel;
import riverlayout.component.RiverLayout;
import controller.AppointmentController;

/**
 * 07-01-2010
 * 
 * V1 Original form with only textfields and 1 textarea for input
 * V2 Date time fields are comboboxes now + datum checks
 * V3 Category combobox added
 * V4 New actionhandler setup + title restriction
 * V5 Checkbox added for whole day appointments
 * 
 * This class is the view for making and editing appointments.
 *
 * @author Stijn Kliemesch 
 */

public class AppointmentView extends JPanel implements Observer {

	private static JFrame frame;
	private static final long serialVersionUID = 04;

	//Reference is the singleton reference
	private static AppointmentView reference;

	//The panels to split the form in 2 equal parts
	//One for the general field and the other for the description textarea
	private JPanel generalFieldsPanel = new JPanel();
	private JPanel descriptionFieldPanel = new JPanel();

	private JLabel titleLabel = new JLabel("Titel");
	private JLabel startLabel = new JLabel("Start");
	private JLabel endLabel = new JLabel("Einde");
	private JLabel locationLabel = new JLabel("Locatie");
	private JLabel contactPersonLabel = new JLabel("Contactpersoon");
	private JLabel errorLabel = new JLabel();
	private JLabel errorDetailsLabel = new JLabel();
	private JLabel descriptionLabel = new JLabel("Beschrijving");
	private JLabel categoryLabel = new JLabel("Categorie");

	//The general fields
	private JTextField titleTextField = new JTextField();
	private JTextField locationTextField = new JTextField();
	private JTextField contactPersonTextField = new JTextField();

	//The start time and date fields
	private JComboBox startMinuteField =  new JComboBox();
	private JComboBox startHourField = new JComboBox();
	private JComboBox startDayField = new JComboBox();
	private JComboBox startMonthField = new JComboBox();
	private JComboBox startYearField = new JComboBox();

	//The end time and date fields
	private JComboBox endMinuteField = new JComboBox();
	private JComboBox endHourField = new JComboBox();
	private JComboBox endDayField = new JComboBox();
	private JComboBox endMonthField = new JComboBox();
	private JComboBox endYearField = new JComboBox();

	private JComboBox categoryField = new JComboBox();
	private JTextArea descriptionTextArea = new JTextArea();

	private JButton cancelButton = new JButton("Annuleren");
	private JButton deleteButton = new JButton("Verwijderen");

	private JCheckBox wholeDayCheckBox = new JCheckBox("Hele dagen");

	URL urlIcon = null;
	//This will be either the save or the edit button,
	//depending on the showNewOrEditAppointmentOnBool() request.
	private JButton actionButton = new JButton();

	private AppointmentController controller;

	private int appointmentId;
	private boolean hasErrors = false;

	private static SaveNewButtonHandler saveNewButtonHandler;
	private static SaveEditButtonHandler saveEditButtonHandler;

	/**
	 * This method must be called to (start and) get the instance.
	 */
	public static synchronized AppointmentView getAppointmentView(AppointmentController controller) {
		if (reference == null) {
			reference = new AppointmentView(controller);
		}
		return reference;
	}

	/**
	 * @throws CloneNotSupportedException
	 */
	public Object clone() throws CloneNotSupportedException {
		throw new CloneNotSupportedException(); 
	}

	/**
	 * The constructor is private to make the class a singleton.
	 */
	private AppointmentView(AppointmentController newController) {

		try {
			urlIcon = getClass().getClassLoader().getResource("pics/icon.jpg");
		} catch(NullPointerException x) {
			x.printStackTrace();
			throw x;
		}

		controller = newController;

		//initalise actionhandlers
		saveNewButtonHandler = new SaveNewButtonHandler();
		saveEditButtonHandler = new SaveEditButtonHandler();

		//Setup of the frame
		frame = new JFrame();
		frame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
		frame.setSize(750, 320);
		frame.setContentPane(this);
		frame.requestFocus(); 
		frame.setTitle("Nieuwe afspraak");
		frame.setResizable(false);
		frame.setIconImage(new ImageIcon(urlIcon).getImage());

		//Setup of the comboboxes
		startMinuteField.addItem("00");
		startMinuteField.addItem("15");
		startMinuteField.addItem("30");
		startMinuteField.addItem("45");
		endMinuteField.addItem("00");
		endMinuteField.addItem("15");
		endMinuteField.addItem("30");
		endMinuteField.addItem("45");
		for(int i = 0; i <= 23; i++) {
			startHourField.addItem(i);
			endHourField.addItem(i);
		}
		for(int i = 1; i <= 31; i++) {
			startDayField.addItem(i);
			endDayField.addItem(i);
		}
		for(int i = 1; i <= 12; i++) {
			startMonthField.addItem(i);
			endMonthField.addItem(i);
		}
		for(int i = 2010; i <= 2110; i++) {
			startYearField.addItem(i);
			endYearField.addItem(i);
		}

		//fill the category combobox
		categoryField.addItem("Geen");
		fillCategoryField();
		
		titleTextField.setDocument(new MaxCharHandler(40));
		locationTextField.setDocument(new MaxCharHandler(40));
		contactPersonTextField.setDocument(new MaxCharHandler(40));
		
		//Setup of the borders
		titleTextField.setBorder(BorderFactory.createLineBorder(Color.black));
		startMinuteField.setBorder(BorderFactory.createLineBorder(Color.black));
		startHourField.setBorder(BorderFactory.createLineBorder(Color.black));
		startDayField.setBorder(BorderFactory.createLineBorder(Color.black));
		startMonthField.setBorder(BorderFactory.createLineBorder(Color.black));
		startYearField.setBorder(BorderFactory.createLineBorder(Color.black));
		endMinuteField.setBorder(BorderFactory.createLineBorder(Color.black));
		endHourField.setBorder(BorderFactory.createLineBorder(Color.black));
		endDayField.setBorder(BorderFactory.createLineBorder(Color.black));
		endMonthField.setBorder(BorderFactory.createLineBorder(Color.black));
		endYearField.setBorder(BorderFactory.createLineBorder(Color.black));
		locationTextField.setBorder(BorderFactory.createLineBorder(Color.black));
		contactPersonTextField.setBorder(BorderFactory.createLineBorder(Color.black));
		descriptionTextArea.setBorder(BorderFactory.createLineBorder(Color.black));
		categoryField.setBorder(BorderFactory.createLineBorder(Color.black));

		//Adding all the elements starts here
		setLayout(new BorderLayout());

		//Add the panel with general elements to the root JPanel
		add(generalFieldsPanel, BorderLayout.WEST);

		generalFieldsPanel.setLayout(new RiverLayout());

		generalFieldsPanel.add("left", new JLabel("Nieuwe Afspraak"));

		generalFieldsPanel.add("br", titleLabel);
		generalFieldsPanel.add("tab hfill", titleTextField);

		generalFieldsPanel.add("br", wholeDayCheckBox);
		wholeDayCheckBox.addActionListener(new wholeDayChangeListener());


		generalFieldsPanel.add("tab", new JLabel("Uur"));
		generalFieldsPanel.add("tab", new JLabel());
		generalFieldsPanel.add("tab", new JLabel("Minuut"));
		generalFieldsPanel.add("tab", new JLabel());
		generalFieldsPanel.add("tab", new JLabel("Dag"));
		generalFieldsPanel.add("tab", new JLabel());
		generalFieldsPanel.add("tab", new JLabel("Maand"));
		generalFieldsPanel.add("tab", new JLabel());
		generalFieldsPanel.add("tab", new JLabel("Jaar"));

		generalFieldsPanel.add("br", startLabel);
		generalFieldsPanel.add("tab", startHourField);
		generalFieldsPanel.add("tab", new JLabel(":"));
		generalFieldsPanel.add("tab", startMinuteField);
		generalFieldsPanel.add("tab", new JLabel(" "));
		generalFieldsPanel.add("tab", startDayField);
		generalFieldsPanel.add("tab", new JLabel("-"));
		generalFieldsPanel.add("tab", startMonthField);
		generalFieldsPanel.add("tab", new JLabel("-"));
		generalFieldsPanel.add("tab", startYearField);

		generalFieldsPanel.add("br", endLabel);
		generalFieldsPanel.add("tab", endHourField);
		generalFieldsPanel.add("tab", new JLabel(":"));
		generalFieldsPanel.add("tab", endMinuteField);
		generalFieldsPanel.add("tab", new JLabel(" "));
		generalFieldsPanel.add("tab", endDayField);
		generalFieldsPanel.add("tab", new JLabel("-"));
		generalFieldsPanel.add("tab", endMonthField);
		generalFieldsPanel.add("tab", new JLabel("-"));
		generalFieldsPanel.add("tab", endYearField);

		generalFieldsPanel.add("br", locationLabel);
		generalFieldsPanel.add("tab hfill", locationTextField);

		generalFieldsPanel.add("br", contactPersonLabel);
		generalFieldsPanel.add("tab hfill", contactPersonTextField);

		generalFieldsPanel.add("br", categoryLabel);
		generalFieldsPanel.add("tab hfill", categoryField);

		generalFieldsPanel.add("br", errorLabel);
		generalFieldsPanel.add("tab hfill", errorDetailsLabel);
		errorLabel.setForeground(Color.RED);
		errorDetailsLabel.setForeground(Color.RED);

		generalFieldsPanel.add("br vfill", new JLabel());

		generalFieldsPanel.add("br", actionButton);
		generalFieldsPanel.add(deleteButton);
		generalFieldsPanel.add(cancelButton);
		deleteButton.addActionListener(new DeleteButtonHandler());
		cancelButton.addActionListener(new CancelButtonHandler());

		//Add the panel with the description elements to the root JPanel
		add(descriptionFieldPanel, BorderLayout.CENTER);

		descriptionFieldPanel.setLayout(new RiverLayout());

		descriptionFieldPanel.add("left", descriptionLabel);
		descriptionFieldPanel.add("br hfill vfill", descriptionTextArea);
	}

	/**
	 *  This fills the CategoryField.
	 */
	private void fillCategoryField() {
		int itemCount = categoryField.getItemCount();
		for(int i = itemCount - 1; i > 0; i--) {
			categoryField.removeItemAt(i);
		}
		Vector<CategoryDataModel> categories = controller.getAllCategories();
		for(CategoryDataModel c : categories) {
			categoryField.addItem(c);
		}
	}

	//Setter methods start here
	/**
	 * Sets the titleTextField's contents.
	 * 
	 * @param inputText Change the title textfield contents
	 */
	public void setInputTitle(String inputText) {
		titleTextField.setText(inputText);
	}

	/**
	 * Sets the true/false if it's planned in whole days
	 */
	public void setInputIsWholeDays(Boolean wholeDays) {
		wholeDayCheckBox.setSelected(wholeDays);
		ActionListener[] al = wholeDayCheckBox.getActionListeners();
		al[0].actionPerformed(null);
	}

	/**
	 * 
	 * Sets the start time and date in the textfields
	 */
	public void setInputStartDateTime(int year, int month, int day, int hour, int minute) {
		String minuteString;
		if(minute == 0) {
			minuteString = "00";
		} else {
			minuteString = Integer.toString(minute);
		}
		startYearField.setSelectedItem(year);
		startMonthField.setSelectedItem(month);
		startDayField.setSelectedItem(day);
		startHourField.setSelectedItem(hour);
		startMinuteField.setSelectedItem(minuteString);
	}

	/**
	 *  Sets the end time and date in the textfields.
	 */
	public void setInputEndDateTime(int year, int month, int day, int hour, int minute) {
		String minuteString;
		if(minute == 0) {
			minuteString = "00";
		} else {
			minuteString = Integer.toString(minute);
		}
		endYearField.setSelectedItem(year);
		endMonthField.setSelectedItem(month);
		endDayField.setSelectedItem(day);
		endHourField.setSelectedItem(hour);
		endMinuteField.setSelectedItem(minuteString);
	}

	/**
	 * Sets the locationTextField's contents.
	 * 
	 * @param inputText Change the location textfield contents
	 */
	public void setInputLocation(String inputText) {
		locationTextField.setText(inputText);
	}

	/**
	 * Sets the contactpersonTextField's contents.
	 * 
	 * @param inputText Change the contactperson textfield contents
	 */
	public void setInputContactPerson(String inputText) {
		contactPersonTextField.setText(inputText);
	}

	/**
	 * Sets the categoryField's contents.
	 * 
	 * @param inputCategory Select the category
	 */
	public void setInputCategory(CategoryDataModel inputCategory) {
		if(inputCategory == null) {
			categoryField.setSelectedItem("Geen");
		} else {
			int itemCount = categoryField.getItemCount();
			if(itemCount > 1) {
				CategoryDataModel category = null;
				for(int i = 1; i < itemCount; i++) {
					category = (CategoryDataModel) categoryField.getItemAt(i);
					if( category.getId() != inputCategory.getId()) {
						category = null;
					} else {
						break;
					}
				}
				categoryField.setSelectedItem(category);
			}

		}
	}

	/**
	 * Sets the descriptionTextArea's contents.
	 * 
	 * @param inputText Change the description textarea contents
	 */
	public void setInputDescription(String inputText) {
		descriptionTextArea.setText(inputText);
	}

	/**
	 * Sets the appointment id for appointment editing purposes.
	 */
	public void setAppointmentId(int id) {
		appointmentId = id;
	}

	//Getter methods start here
	/**
	 * @return the current text in titleTextField
	 */
	public String getInputTitle() {
		return titleTextField.getText();

	}

	/**
	 * @return the current state wether or not the appointment is scheduled
	 * for the whole day
	 */
	public Boolean getInputIsWholeDays() {
		return wholeDayCheckBox.isSelected();
	}

	/**
	 * 
	 * @return the current text in startYearField
	 */
	public String getInputStartYear() {
		return startYearField.getSelectedItem().toString();
	}

	/**
	 * @return the current text in startMonthsField
	 */
	public String getInputStartMonth() {
		return startMonthField.getSelectedItem().toString();
	}

	/**
	 * @return the current text in startDayField
	 */
	public String getInputStartDay() {
		return startDayField.getSelectedItem().toString();
	}

	/**
	 * @return the current text in startHourField
	 */
	public String getInputStartHour() {
		return startHourField.getSelectedItem().toString();
	}

	/**
	 * @return the current text in startMinuteField
	 */
	public String getInputStartMinute() {
		return startMinuteField.getSelectedItem().toString();
	}

	/**
	 * @return the current text in endYearField
	 */
	public String getInputEndYear() {
		return endYearField.getSelectedItem().toString();
	}

	/**
	 * @return the current text in endMonthField
	 */
	public String getInputEndMonth() {
		return endMonthField.getSelectedItem().toString();
	}

	/**
	 * @return the current text in endDayField
	 */
	public String getInputEndDay() {
		return endDayField.getSelectedItem().toString();
	}

	/**
	 * @return the current text in endHourField
	 */
	public String getInputEndHour() {
		return endHourField.getSelectedItem().toString();
	}

	/**
	 * @return the current text in endMinuteField
	 */
	public String getInputEndMinute() {
		return endMinuteField.getSelectedItem().toString();
	}

	/**
	 * @return the current text in locationTextField
	 */
	public String getInputLocation() {
		return locationTextField.getText();
	}

	/**
	 * @return the current text in contactPersonTextField
	 */
	public String getInputContactPerson() {
		return contactPersonTextField.getText();
	}

	/**
	 * Gets the selected category, returns a null on the top selection,
	 * which is a String.
	 * 
	 * @return the current selection in categoryField
	 */
	public CategoryDataModel getInputCategory() {
		if(categoryField.getSelectedItem() instanceof CategoryDataModel) {
			return (CategoryDataModel) categoryField.getSelectedItem();
		} else {
			return null;
		}
	}

	/**
	 * @return the current text in inputDescriptionTextArea
	 */
	public String getInputDescription() {
		return descriptionTextArea.getText();
	}

	/**
	 * Gets the appointment id for appointment editing purposes.
	 */
	public int getAppointmentId() {
		return appointmentId;
	}

	/**
	 * Returns the starting date and time in the format: "YYYY-MM-DD HH:MM".
	 * 
	 * @return String the formatted start datetime
	 */
	public String getInputStart() {
		String hour, minute;
		if(wholeDayCheckBox.isSelected()) {
			hour = "00";
			minute = "00";
		} else {
			hour = getInputStartHour();
			minute = getInputStartMinute();
		}
		return controller.convertDataToDateTime(getInputStartYear(),
				getInputStartMonth(),
				getInputStartDay(),
				hour,
				minute);
	}

	/**
	 * Returns the ending date and time in the format: "YYYY-MM-DD HH:MM".
	 * 
	 * @return String the formatted end datetime
	 */
	public String getInputEnd() {
		String hour, minute;
		if(wholeDayCheckBox.isSelected()) {
			hour = "23";
			minute = "59";
		} else {
			hour = getInputEndHour();
			minute = getInputEndMinute();
		}
		return controller.convertDataToDateTime(getInputEndYear(),
				getInputEndMonth(),
				getInputEndDay(),
				hour,
				minute);
	}
	//End of getter and setter methods

	/**
	 * @return True if there are errors, otherwise false.
	 */
	public boolean hasErrors() {
		resetErrors();
		if(!controller.isValidDate(getInputStart())) {
			setError("StartDateInvalid");
		} 
		if(!controller.isValidDate(getInputEnd())) {
			setError("EndDateInvalid");
		}
		if(titleTextField.getText().equals("")) {
			setError("TitleEmpty");
		}
		if(!controller.startIsBeforeEnd(getInputStart(), getInputEnd())) {
			setError("startIsNotBeforeEnd");
		}
		return hasErrors;
	}

	/**
	 * Resets the private instance variable, error messages and label colors.
	 */
	public void resetErrors() {
		hasErrors = false;
		errorLabel.setText("");
		errorDetailsLabel.setText("");
		startLabel.setForeground(Color.BLACK);
		endLabel.setForeground(Color.BLACK);
		titleLabel.setForeground(Color.BLACK);
	}

	/**
	 * Set an error, Dates validated through gregorian calendar date system,
	 * If there already is an error active before this is called, no changes will be made
	 * 
	 * Possible errors:
	 * -StartDateInvalid The start date fields do not contain a valid date 
	 * -EndDateInvalid The end date fields do not contain a valid date
	 * -startIsNotBeforeEnd The starting datetime is not before the ending datetime
	 * -TitleEmpty The title is empty
	 * 
	 * @param errorType the error command
	 */
	private void setError(String errorType) {
		if(!hasErrors) {
			hasErrors = true;
			errorLabel.setText("Error");
			errorDetailsLabel.setText("De volgende velden zijn incorrect:");
		}
		if(errorType.equals("StartDateInvalid")) {
			errorDetailsLabel.setText(errorDetailsLabel.getText()+" Start");
			startLabel.setForeground(Color.RED);
		}
		if(errorType.equals("EndDateInvalid")) {
			errorDetailsLabel.setText(errorDetailsLabel.getText()+" Eind");
			endLabel.setForeground(Color.RED);
		}
		if(errorType.equals("TitleEmpty")) {
			errorDetailsLabel.setText(errorDetailsLabel.getText()+" Titel");
			titleLabel.setForeground(Color.RED);
		}
		if(errorType.equals("startIsNotBeforeEnd")) {
			errorDetailsLabel.setText("Startdatum is niet voor de einddatum");
			startLabel.setForeground(Color.RED);
			endLabel.setForeground(Color.RED);
		}
		hasErrors = true;
	}

	/**
	 * This method is called whenever the observed object is changed.
	 * 
	 * @param observable
	 * @param argument
	 */
	public void update(Observable observable, Object argument) {
		hide();
	}

	/**
	 * Hides the window.
	 */
	public void hide() {
		frame.setVisible(false);
	}

	/**
	 * Shows the window. This will be as the setup for a new appointment.
	 * 
	 * @param newAppointment True if it's a new appointment, false if it exists
	 */
	public void showNewOrEditAppointmentOnBool(boolean newAppointment) {
		fillCategoryField();
		if(newAppointment) {
			actionButton.setText("Opslaan");
			actionButton.removeActionListener(saveNewButtonHandler);
			actionButton.removeActionListener(saveEditButtonHandler);
			actionButton.addActionListener(saveNewButtonHandler);
			deleteButton.setEnabled(false);
			frame.setTitle("Nieuwe afspraak");
		} else {
			actionButton.setText("Wijzigen");
			actionButton.removeActionListener(saveEditButtonHandler);
			actionButton.removeActionListener(saveNewButtonHandler);
			actionButton.addActionListener(saveEditButtonHandler);
			deleteButton.setEnabled(true);
			frame.setTitle("Wijzig afspraak");
		}
		updateUI();
		frame.setVisible(true);
	}

	/**
	 * @deprecated Use showNewOrEditAppointmentOnBool(boolean newAppointment) instead.
	 */
	public void show() {
		throw new UnsupportedOperationException();
	}

	/**
	 * @author Stijn Kliemesch
	 * 
	 * This class is the actionlistener for the cancel button.
	 */
	private class CancelButtonHandler implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent arg0) {
			hide();
		}
	}

	/**
	 * This class saves a new appointment.
	 */
	private class SaveNewButtonHandler implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent e) {
			if(!hasErrors()) {
				controller.addAppointment(new model.AppointmentDataModel(0, getInputTitle(), util.CalendarConvert.convertDateStringToCalendar(getInputStart()), util.CalendarConvert.convertDateStringToCalendar(getInputEnd()), getInputLocation(), getInputContactPerson(), getInputCategory(), getInputDescription()));
			}			
		}
	}

	/**
	 * This class saved the edited version of the appointment.
	 */
	private class SaveEditButtonHandler implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent e) {
			if(!hasErrors()) {
				controller.updateAppointment(new model.AppointmentDataModel(getAppointmentId(), getInputTitle(), util.CalendarConvert.convertDateStringToCalendar(getInputStart()), util.CalendarConvert.convertDateStringToCalendar(getInputEnd()), getInputLocation(), getInputContactPerson(), getInputCategory(), getInputDescription()));
			}
		}
	}

	/**
	 * This class deletes the appointment.
	 */
	private class DeleteButtonHandler implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent e) {
			controller.deleteAppointment(getAppointmentId());
		}
	}

	/**
	 * This class listens and performs whenever you click the checkbox wholeday when you create a new appointment.
	 */
	private class wholeDayChangeListener implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent arg0) {
			if(wholeDayCheckBox.isSelected()) {
				startMinuteField.setEnabled(false);
				startHourField.setEnabled(false);
				endMinuteField.setEnabled(false);
				endHourField.setEnabled(false);
			} else {
				startMinuteField.setEnabled(true);
				startHourField.setEnabled(true);
				endMinuteField.setEnabled(true);
				endHourField.setEnabled(true);
			}
		}
	}

	/**
	 * This class defines the maximum characters for jtextfields.
	 */
	private class MaxCharHandler extends PlainDocument {
		private static final long serialVersionUID = 1L;
		private int max = 10;
		
		public MaxCharHandler(int newMax) {
			max = newMax;
		} 

		public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
			if (getLength()+str.length()>max) {
				str = str.substring(0, max - getLength());
			}
			super.insertString(offs, str, a);
		}
	}
}
